/* 
 * This code is part of the project "NUMA-aware Graph-structured Analytics"
 * 
 *
 * Copyright (C) 2014 Institute of Parallel And Distributed Systems (IPADS), Shanghai Jiao Tong University
 *     All rights reserved 
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 * For more about this software, visit:
 *
 *     http://ipads.se.sjtu.edu.cn/projects/polymer.html
 *
 */

#include "polymer-wgh.h"
#include "gettime.h"
#include "math.h"

#include <pthread.h>
#include <sys/mman.h>
#include <numa.h>
using namespace std;

#define PAGE_SIZE (4096)

int CORES_PER_NODE = 6;

volatile int shouldStart = 0;

intE *ShortestPathLen_global = NULL;
int *Visited_global = NULL;

double *p_ans = NULL;
intT vPerNode = 0;
int numOfNode = 0;

bool needResult = false;

pthread_barrier_t barr;
pthread_barrier_t global_barr;
pthread_mutex_t mut;

volatile int global_counter = 0;
volatile int global_toggle = 0;

vertices *Frontier;

void *graph_ptr;

struct BF_F {
    intE* ShortestPathLen;
    int* Visited;
    BF_F(intE* _ShortestPathLen, int* _Visited) : 
	ShortestPathLen(_ShortestPathLen), Visited(_Visited) {}

    inline void *nextPrefetchAddr(intT index) {
	return &ShortestPathLen[index];
    }

    inline bool update (intT s, intT d, intE edgeLen) { //Update ShortestPathLen if found a shorter path
	intE newDist = ShortestPathLen[s] + edgeLen;
	if(ShortestPathLen[d] > newDist) {
	    ShortestPathLen[d] = newDist;
	    if(Visited[d] == 0) { Visited[d] = 1 ; return 1;}
	}
	return 0;
    }
    inline bool updateAtomic (intT s, intT d, intE edgeLen){ //atomic Update
	intE newDist = ShortestPathLen[s] + edgeLen;
	return (writeMin(&ShortestPathLen[d],newDist) &&
		CAS(&Visited[d],0,1));
    }
    inline bool cond (intT d) { return 1; } //does nothing
};

//reset visited vertices
struct BF_Vertex_F {
    int* Visited;
    BF_Vertex_F(int* _Visited) : Visited(_Visited) {}
    inline bool operator() (intT i){
	Visited[i] = 0;
	return 1;
    }
};

struct BF_worker_arg {
    void *GA;
    int tid;
    int numOfNode;
    intT start;
    intT rangeLow;
    intT rangeHi;
};

struct BF_subworker_arg {
    void *GA;
    int tid;
    int subTid;
    intT startPos;
    intT endPos;
    intT rangeLow;
    intT rangeHi;
    intE **ShortestPathLen_ptr;
    int **Visited_ptr;
    pthread_barrier_t *node_barr;
    pthread_barrier_t *node_barr2;
    LocalFrontier *localFrontier;

    volatile int *local_custom_counter;
    volatile int *local_custom_toggle;
};

template <class vertex>
void *BFSubWorker(void *arg) {
    BF_subworker_arg *my_arg = (BF_subworker_arg *)arg;
    wghGraph<vertex> &GA = *(wghGraph<vertex> *)my_arg->GA;
    const intT n = GA.n;
    int tid = my_arg->tid;
    int subTid = my_arg->subTid;
    pthread_barrier_t *local_barr = my_arg->node_barr;
    LocalFrontier *output = my_arg->localFrontier;

    intE *ShortestPathLen = *(my_arg->ShortestPathLen_ptr);
    int *Visited = *(my_arg->Visited_ptr);
    
    int currIter = 0;
    intT rangeLow = my_arg->rangeLow;
    intT rangeHi = my_arg->rangeHi;

    intT start = my_arg->startPos;
    intT end = my_arg->endPos;

    intT numVisited = 0;

    Custom_barrier local_custom(my_arg->local_custom_counter, my_arg->local_custom_toggle, CORES_PER_NODE);
    Custom_barrier global_custom(&global_counter, &global_toggle, Frontier->numOfNodes);

    Subworker_Partitioner subworker(CORES_PER_NODE);
    subworker.tid = tid;
    subworker.subTid = subTid;
    subworker.dense_start = start;
    subworker.dense_end = end;
    subworker.global_barr = &global_barr;
    subworker.local_barr = my_arg->node_barr2;
    subworker.local_custom = local_custom;
    subworker.subMaster_custom = global_custom;

    pthread_barrier_wait(local_barr);
    if (subworker.isMaster())
	printf("started\n");
    if (subworker.isSubMaster()) {
	Frontier->calculateNumOfNonZero(tid);
    }
    pthread_barrier_wait(&global_barr);
    while(!Frontier->isEmpty() || currIter == 0){ //loop until frontier is empty
	currIter++;
	if (tid + subTid == 0) {
	    numVisited += Frontier->numNonzeros();
	    printf("Round %d: num of non zeros: %" PRIintT "\t", currIter, Frontier->numNonzeros());
	}

	if (subTid == 0) {
	    //{parallel_for(intT i=output->startID;i<output->endID;i++) output->setBit(i, false);}
	}
	//pthread_barrier_wait(&global_barr);
	//subworker.globalWait();
	//apply edgemap
	struct timeval startT, endT;
	struct timezone tz = {0, 0};
	gettimeofday(&startT, &tz);
	edgeMap(GA, Frontier, BF_F(ShortestPathLen, Visited), output, GA.m/20, DENSE_FORWARD, false, true, subworker);
	//pthread_barrier_wait(&global_barr);
	subworker.globalWait();
        vertexMap(Frontier, BF_Vertex_F(Visited), tid, subTid, CORES_PER_NODE);
	vertexCounter(GA, output, tid, subTid, CORES_PER_NODE);
	//edgeMapSparseAsync(GA, Frontier, BF_F(parents), output, subworker);
	if (subTid == 0) {
	    //pthread_barrier_wait(&global_barr);
	    subworker.globalWait();
	    switchFrontier(tid, Frontier, output); //set new frontier
	} else {
	    output = Frontier->getFrontier(tid);
	    //pthread_barrier_wait(&global_barr);
	    subworker.globalWait();
	}
	gettimeofday(&endT, &tz);
	double timeStart = ((double)startT.tv_sec) + ((double)startT.tv_usec) / 1000000.0;
	double timeEnd = ((double)endT.tv_sec) + ((double)endT.tv_usec) / 1000000.0;

	double mapTime = timeEnd - timeStart;
	if (tid + subTid == 0) {
	    printf("iteration time: %lf\n", mapTime);
	}
	if (subworker.isSubMaster()) {
	    Frontier->calculateNumOfNonZero(tid);	   	  	  	    
	}
	//pthread_barrier_wait(&global_barr);
	subworker.globalWait();
	//break;
    }

    if (tid + subTid == 0) {
	cout << "Vertices visited = " << numVisited << "\n";
	cout << "Finished in " << currIter << " iterations\n";
    }
    pthread_barrier_wait(local_barr);
    return NULL;
}

pthread_barrier_t timerBarr;

template <class vertex>
void *BFThread(void *arg) {
    BF_worker_arg *my_arg = (BF_worker_arg *)arg;
    wghGraph<vertex> &GA = *(wghGraph<vertex> *)my_arg->GA;
    int tid = my_arg->tid;

    char nodeString[10];
    sprintf(nodeString, "%d", tid);
    struct bitmask *nodemask = numa_parse_nodestring(nodeString);
    numa_bind(nodemask);

    intT rangeLow = my_arg->rangeLow;
    intT rangeHi = my_arg->rangeHi;

    wghGraph<vertex> localGraph = graphFilter(GA, rangeLow, rangeHi);

    intT sizeOfShards[CORES_PER_NODE];

    subPartitionByDegree(localGraph, CORES_PER_NODE, sizeOfShards, sizeof(intT), true, true);
    
    for (int i = 0; i < CORES_PER_NODE; i++) {
	//printf("subPartition: %d %d: %d\n", tid, i, sizeOfShards[i]);
    }

    while (shouldStart == 0) ;
    printf("over filtering\n");
    /*
      if (0 != __cilkrts_set_param("nworkers","1")) {
      printf("set failed: %d\n", tid);
      }
    */

    const intT n = GA.n;
    int numOfT = my_arg->numOfNode;

    intT blockSize = rangeHi - rangeLow;

    //printf("blockSizeof %d: %d low: %d high: %d\n", tid, blockSize, rangeLow, rangeHi);

    double one_over_n = 1/(double)n;
    
    
    intE *ShortestPathLen = ShortestPathLen_global;
    int *Visited = Visited_global;
    bool* frontier = (bool *)numa_alloc_local(sizeof(bool) * blockSize);
    
    /*
      double* p_curr = (double *)malloc(sizeof(double) * blockSize);
      double* p_next = (double *)malloc(sizeof(double) * blockSize);
      bool* frontier = (bool *)malloc(sizeof(bool) * blockSize);
    */
    
    /*
      if (tid == 0)
      startTime();
    */
    double mapTime = 0.0;
    struct timeval start, end;
    struct timezone tz = {0, 0};

    for(intT i=rangeLow;i<rangeHi;i++) ShortestPathLen[i] = INT_MAX/2;
    for(intT i=rangeLow;i<rangeHi;i++) Visited[i] = 0;
    for(intT i=0;i<blockSize;i++) frontier[i] = false;
    if (tid == 0)
	Frontier = new vertices(numOfT);

    //printf("register %d: %p\n", tid, frontier);
    
    LocalFrontier *current = new LocalFrontier(frontier, rangeLow, rangeHi);

    bool* next = (bool *)numa_alloc_local(sizeof(bool) * blockSize);
    for(intT i=0;i<blockSize;i++) next[i] = false;
    LocalFrontier *output = new LocalFrontier(next, rangeLow, rangeHi);

    pthread_barrier_wait(&barr);
    
    Frontier->registerFrontier(tid, current);

    pthread_barrier_wait(&barr);

    if (tid == 0) {
	Frontier->calculateOffsets();
	Frontier->setBit(my_arg->start, true);
    }

    if (my_arg->start >= rangeLow && my_arg->start < rangeHi) {
	current->m = 1;
	current->outEdgesCount = GA.V[my_arg->start].getOutDegree();
	ShortestPathLen[my_arg->start] = 0;
    } else {
	current->m = 0;
	current->outEdgesCount = 0;
    }

    pthread_barrier_t localBarr;
    pthread_barrier_init(&localBarr, NULL, CORES_PER_NODE+1);

    pthread_barrier_t localBarr2;
    pthread_barrier_init(&localBarr2, NULL, CORES_PER_NODE);

    intT startPos = 0;

    pthread_t subTids[CORES_PER_NODE];    
    
    volatile int local_counter = 0;
    volatile int local_toggle = 0;

    for (int i = 0; i < CORES_PER_NODE; i++) {	
	BF_subworker_arg *arg = (BF_subworker_arg *)malloc(sizeof(BF_subworker_arg));
	arg->GA = (void *)(&localGraph);
	arg->tid = tid;
	arg->subTid = i;
	arg->rangeLow = rangeLow;
	arg->rangeHi = rangeHi;
	arg->ShortestPathLen_ptr = &ShortestPathLen;
	arg->Visited_ptr = &Visited;
	arg->node_barr = &localBarr;
	arg->node_barr2 = &localBarr2;
	arg->localFrontier = output;

	arg->local_custom_counter = &local_counter;
	arg->local_custom_toggle = &local_toggle;
	
	arg->startPos = startPos;
	arg->endPos = startPos + sizeOfShards[i];
	startPos = arg->endPos;
        pthread_create(&subTids[i], NULL, BFSubWorker<vertex>, (void *)arg);
    }

    pthread_barrier_wait(&barr);
    if (tid == 0)
	GA.del();
    pthread_barrier_wait(&barr);

    pthread_barrier_wait(&timerBarr);

    pthread_barrier_wait(&barr);

    pthread_barrier_wait(&localBarr);

    pthread_barrier_wait(&localBarr);

    pthread_barrier_wait(&barr);
    return NULL;
}

struct BF_Hash_F {
    int shardNum;
    intT vertPerShard;
    intT n;
    BF_Hash_F(intT _n, int _shardNum):n(_n), shardNum(_shardNum), vertPerShard(_n / _shardNum){}
    
    inline intT hashFunc(intT index) {
	if (index >= shardNum * vertPerShard) {
	    return index;
	}
	intT idxOfShard = index % shardNum;
	intT idxInShard = index / shardNum;
	return (idxOfShard * vertPerShard + idxInShard);
    }

    inline intT hashBackFunc(intT index) {
	if (index >= shardNum * vertPerShard) {
	    return index;
	}
	intT idxOfShard = index / vertPerShard;
	intT idxInShard = index % vertPerShard;
	return (idxOfShard + idxInShard * shardNum);
    }
};

template <class vertex>
void BF_main(wghGraph<vertex> &GA, intT start) {
    numOfNode = numa_num_configured_nodes();
    vPerNode = GA.n / numOfNode;
    CORES_PER_NODE = numa_num_configured_cpus() / numOfNode;
    pthread_barrier_init(&barr, NULL, numOfNode);
    pthread_barrier_init(&timerBarr, NULL, numOfNode+1);
    pthread_barrier_init(&global_barr, NULL, CORES_PER_NODE * numOfNode);
    pthread_mutex_init(&mut, NULL);
    intT sizeArr[numOfNode];
    BF_Hash_F hasher(GA.n, numOfNode);
    graphHasher(GA, hasher);
    partitionByDegree(GA, numOfNode, sizeArr, sizeof(intT));
    /*
    intT vertPerPage = PAGESIZE / sizeof(double);
    intT subShardSize = ((GA.n / numOfNode) / vertPerPage) * vertPerPage;
    for (int i = 0; i < numOfNode - 1; i++) {
	sizeArr[i] = subShardSize;
    }
    sizeArr[numOfNode - 1] = GA.n - subShardSize * (numOfNode - 1);
    */
    ShortestPathLen_global = (intE *)mapDataArray(numOfNode, sizeArr, sizeof(intE));
    Visited_global = (int *)mapDataArray(numOfNode, sizeArr, sizeof(int));

    printf("start create %d threads\n", numOfNode);
    pthread_t tids[numOfNode];
    intT prev = 0;
    graph_ptr = (void *)(&GA);
    for (int i = 0; i < numOfNode; i++) {
	BF_worker_arg *arg = (BF_worker_arg *)malloc(sizeof(BF_worker_arg));
	arg->GA = (void *)(&GA);
	arg->tid = i;
	arg->numOfNode = numOfNode;
	arg->start = hasher.hashFunc(start);
	arg->rangeLow = prev;
	arg->rangeHi = prev + sizeArr[i];
	prev = prev + sizeArr[i];
	pthread_create(&tids[i], NULL, BFThread<vertex>, (void *)arg);
    }
    shouldStart = 1;
    pthread_barrier_wait(&timerBarr);
    //nextTime("Graph Partition");
    startTime();
    printf("all created\n");
    for (int i = 0; i < numOfNode; i++) {
	pthread_join(tids[i], NULL);
    }
    nextTime("BellmanFord");
    if (needResult) {
	for (intT i = 0; i < GA.n; i++) {
	    cout << i << "\t" << std::scientific << std::setprecision(9) << p_ans[hasher.hashFunc(i)] << "\n";
	}
    }
}

int parallel_main(int argc, char* argv[]) {  
    char* iFile;
    bool binary = false;
    bool symmetric = false;
    intT startPos = 1;
    needResult = false;
    if(argc > 1) iFile = argv[1];
    if(argc > 2) startPos = atoi(argv[2]);
    if(argc > 3) if((string) argv[3] == (string) "-result") needResult = true;
    if(argc > 4) if((string) argv[4] == (string) "-s") symmetric = true;
    if(argc > 5) if((string) argv[5] == (string) "-b") binary = true;
    numa_set_interleave_mask(numa_all_nodes_ptr);
    if(symmetric) {
	wghGraph<symmetricWghVertex> WG = 
	    readWghGraph<symmetricWghVertex>(iFile,symmetric,binary);
	BF_main(WG, (intT)startPos);
	//WG.del(); 
    } else {
	wghGraph<asymmetricWghVertex> WG = 
	    readWghGraph<asymmetricWghVertex>(iFile,symmetric,binary);
	BF_main(WG, (intT)startPos);
	//WG.del();
    }
    return 0;
}
